package de.jmda.gen.sample;

import static de.jmda.core.mproc.ProcessingUtilities.getMethods;
import static de.jmda.core.mproc.ProcessingUtilities.getPackageQualifiedName;
import static de.jmda.core.mproc.ProcessingUtilities.isVisibilityPublic;
import static de.jmda.core.util.FileUtil.toPackagePath;
import static de.jmda.gen.java.JavaGeneratorUtils.asDeclaration;
import static de.jmda.gen.java.naming.ImportManagerProvider.getImportManager;
import static de.jmda.gen.java.naming.ImportManagerProvider.newImportManager;
import static de.jmda.gen.java.naming.ImportManagerProvider.restoreImportManager;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import de.jmda.gen.GeneratorException;
import de.jmda.gen.LineIndenter;
import de.jmda.gen.java.InstanceMethodGenerator;
import de.jmda.gen.java.InterfaceGenerator;
import de.jmda.gen.java.ModifiersUtil.VisibilityModifier;
import de.jmda.gen.java.impl.DefaultInstanceMethodGenerator;
import de.jmda.gen.java.impl.DefaultInterfaceGenerator;
import de.jmda.gen.java.naming.ImportManager;

public class ExtractInterfaceTool
{
	private final static Logger LOGGER = Logger.getLogger(ExtractInterfaceTool.class);
	
	private final static Set<String> NAMES_OF_GENERATED_TYPES = new HashSet<>();

	protected InterfaceGenerator generator = new DefaultInterfaceGenerator();
	
	private TypeElement typeElement;

	protected String genTargetPackageName;
	
	protected String genTargetNameSimple;
	
	private ImportManager backup;

	public ExtractInterfaceTool(
			TypeElement typeElement,
			String genTargetPackageName,
			String genTargetNameSimple)
  {
	  super();

	  this.typeElement = typeElement;

	  this.genTargetPackageName = genTargetPackageName;
	  this.genTargetNameSimple = genTargetNameSimple;

	  backup = newImportManager(genTargetPackageName, genTargetNameSimple);
  }

	public ExtractInterfaceTool(TypeElement typeElement)
  {
	  this(
				typeElement,
				getPackageQualifiedName(typeElement) + ".gen",
				typeElement.getSimpleName().toString());
  }
	
	public void extract() throws GeneratorException
	{
		generator.setPackageName(genTargetPackageName);
		generator.setCompilationUnitTypeName(genTargetNameSimple);
		generator.setVisibility(VisibilityModifier.PUBLIC);
		
		for (ExecutableElement method : getMethods(typeElement))
		{
			handle(method);
		}

		generator.addImportStatementGenerators(
				getImportManager().getImportStatementGenerators());
		
    try
    {
	    FileUtils.writeStringToFile(
	    		new File(
	    				"src/gen/java",
	    				toPackagePath(genTargetPackageName) + "/" +
	    				genTargetNameSimple + ".java"),
	    		generator.generate().toString());

	    if (false == NAMES_OF_GENERATED_TYPES.add(genTargetNameSimple))
	    {
	    	LOGGER.error("name conflict for " + genTargetNameSimple);
	    }
    }
    catch (IOException e)
    {
    	throw new GeneratorException("failure writing to file", e);
    }
    finally
    {
  		restoreImportManager(backup);
    }
	}

	protected void handle(ExecutableElement method)
  {
		if (isVisibilityPublic(method))
		{
			InstanceMethodGenerator methodGenerator =
					new DefaultInstanceMethodGenerator();

		  methodGenerator.setLineIndenter(new LineIndenter("\t", 1));
			methodGenerator.setInterfaceMethodDeclarationGenerator(true);

			methodGenerator.setType(getTypeForMethod(method.getReturnType()));
			methodGenerator.setName(method.getSimpleName().toString());

			for (VariableElement variableElement : method.getParameters())
			{
        methodGenerator.addParameter(
        		getTypeForMethod(variableElement.asType()),
        		variableElement.getSimpleName().toString());
			}

			generator.addMethodDeclarationGenerator(methodGenerator);
		}
  }
	
	protected String getTypeForMethod(TypeMirror typeMirror)
	{
		return asDeclaration(typeMirror);
	}
}